﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using Clandestine.Battle.Spells;
using Clandestine.Battle.Spells.Arcane;
using System.Threading;

namespace Clandestine.Battle
{
    public enum BattleType
    {
        Normal,
        Ambush, // Player ambushed
        SneakAttack // Player ambushes
    }

    public enum BattleAction
    {
        Attack, 
        Defend,
        Arcane,
        Divine,
        Items
    }

    public delegate void BlankDelegate(); // I like this.

    public class BattleController
    {

        public static AnimatedSprite SpellRing = new AnimatedSprite(); // No where better for this.
        public List<Player> Players = new List<Player>();
        public List<Enemy> Enemies = new List<Enemy>();
        public AnimationQueue Queue = new AnimationQueue();
        public EventWaitHandle FinishedSignal = new EventWaitHandle(false, EventResetMode.AutoReset);
        private Menu cmdMenu;
        private BattleType type;
        private AnimatedSprite cursor = new AnimatedSprite();
        private StatusWindow status;
        private BattleAction turnAction = BattleAction.Arcane;

        public BattleController(BattleType type, List<Player> players, List<Enemy> enemies)
        {
            this.Players = players;
            this.Enemies = enemies;
            this.type = type;

            // Initialise Sprites
            foreach (Combatant c in players)
                c.InitialiseSprite();
            foreach (Combatant c in enemies)
                c.InitialiseSprite();

            // Setup menu
            cmdMenu = new Menu(new Size(139, 95), Alignment.Right, new Point(0, 0), new MenuItem[] { });
            cmdMenu.Position = new Point(2, Graphics.ScreenHeight - 2 - cmdMenu.Height);

            // Setup spellring (even though it's static?!)
            SpellRing.AddAnimation("spell", new Animation("spellring.png", 48, 48, 0, 0, 0, 3));
            SpellRing.Layer = 0f;
            SpellRing.Interval = 50;
            SpellRing.Visible = false;
            SpellRing.Playing = false;

            // Setup status -- will move to a new class..
            status = new StatusWindow(Players, new Point(4 + cmdMenu.Width, Graphics.ScreenHeight - 2 - cmdMenu.Height));
            
            // Type of battle
            MessageWindow.Sprite.Position = new Point(2, 2);
            switch (type)
            {
                case BattleType.Ambush:
                    MessageWindow.SetText("AMBUSH!", 5000);
                    break;
                case BattleType.SneakAttack:
                    MessageWindow.SetText("SNEAK ATTACK! YEAHHH!", 5000);
                    break;
            }

            // Setup cursor
            cursor.AddAnimation("bounce", new Animation("rcursor_anim.png", 19, 13, 0, 0, 0, 5));
            cursor.Visible = false;
            cursor.Playing = false;
            cursor.Interval = 100;

            // Events
            foreach (Enemy e in enemies)
            {
                e.OnDie += new CombatantDie(OnEnemyDie);
                e.OnDamage += new CombatantDamage(OnEnemyDamage);
                e.OnHeal += new CombatantHeal(OnHeal);
                e.OnAffliction += new CombatantAfflicted(OnAffliction);
            }
            foreach (Player p in players)
            {
                p.OnDamage += new CombatantDamage(OnEnemyDamage);
                p.OnHeal += new CombatantHeal(OnHeal);
                p.OnAffliction += new CombatantAfflicted(OnAffliction);
            }

            // Position sprites -- uglyy
            // Enemy
            int totalHeight = 0;
            foreach (Enemy e in enemies)
                totalHeight += e.Sprite.UnscaledHeight;
            int spacingEnemy = (((Graphics.ScreenHeight - cmdMenu.Height - 2) - MessageWindow.Height - totalHeight) / (players.Count+1));
            int pos = spacingEnemy;
            for (int i = 0; i < enemies.Count; i++)
            {
                enemies[i].Sprite.Position = new Point(50, MessageWindow.Height + pos);
                pos += enemies[i].Sprite.UnscaledHeight + spacingEnemy;
                enemies[i].Sprite.Visible = true;
            }
            // Player
            int spacingPlayer = ((Graphics.ScreenHeight - cmdMenu.Height - MessageWindow.Height - 2) / (players.Count+1));
            for (int i = 0; i < players.Count; i++)
            {
                players[i].Sprite.Position = new Point(Graphics.ScreenWidth - 48 - 50, MessageWindow.Height + (spacingPlayer * (i + 1)) - 24);
                players[i].Sprite.Visible = true;
            }
        }

        public static void StartBattle(BattleType type, List<Player> players, List<Enemy> enemies)
        {
            // Make the world map invisible, and reset camera :)
            WorldMap.Paused = true;
            List<Renderable> worldRenderables;
            lock (Graphics.Renderables)
            {
               worldRenderables = Graphics.Renderables;
               Graphics.Renderables.Clear();
            }
            PointF cameraPos = Camera.Position;
            Camera.Position = new Point(0, 0);

            // Make battle
            BattleController battle = new BattleController(type, players, enemies);

            // Our thread -- we REALLY don't need this, I quite like hijacking WorldMap's ;)
            Program.BattleThread = new Thread(new ParameterizedThreadStart(battle.Loop));
            Program.BattleThread.Start();

            // Wait till finished
            battle.FinishedSignal.WaitOne();

            // In theory, we don't need to dispose any of our stuff now,
            // if we set the Graphics.Renderables to what it was originally, all our stuff here will have no
            // references to it, as long as this battle is .Dispose()d, so should be GCed. YAY.
            //battle.Dispose();

            // Reset stuff
            lock (Graphics.Renderables)
                Graphics.Renderables = worldRenderables;
            Camera.Position = cameraPos;
            WorldMap.Paused = false;
        }

        public void Loop(object o)
        {
            while (Program.Running)
            {

                // Setup the Command Menu
                cmdMenu.Enabled = true;
                cmdMenu.Items = new MenuItem[] { 
                        new MenuItem("Attack", delegate {
                            turnAction = BattleAction.Attack;
                        }), 
                        new MenuItem("Defend", delegate {
                            turnAction = BattleAction.Defend;
                        }),
                        new MenuItem("Arcane", delegate {
                            turnAction = BattleAction.Arcane;
                        }),
                        new MenuItem("Divine", delegate {
                            turnAction = BattleAction.Divine;
                        }),
                        new MenuItem("Items", delegate {
                            turnAction = BattleAction.Items;
                        })
                    };
                cmdMenu.InitializeSprites(); 

                // Go through each member of turnOrder.. in order!
                for (int i = 0; i < Players.Count; i++)
                {
                    if (!Players[i].Asleep)
                    {
                        Log.i("Battle: Starting {0}'s turn.", Players[i].Name);

                        // Poison
                        if (Players[i].Poisoned)
                        {
                            MessageWindow.SetText(Players[i].Name + " takes poison damage!", 3000);
                            Players[i].TakeDamage(Players[i].PoisonDamage);
                        }

                        // Do menus etc.
                        doTurn(Players[i]);
                        Queue.PlayQueue();
                        Log.i("Battle: Ending {0}'s turn.", Players[i].Name);
                    }
                }

                // Hide the menu items
                cmdMenu.Items = new MenuItem[] { }; 
                cursor.Visible = false;
                cursor.Playing = false;
                status.HideCursor();

                // The player will have now selected actions for all playable characters, 
                // we should now have a nice animation queue to go through and animate!


                // AI 
                for (int i = 0; i < Enemies.Count; i++)
                    if (!Enemies[i].Asleep)
                    {
                        if (Enemies[i].Poisoned)
                        {
                            MessageWindow.SetText(Enemies[i].Name + " takes poison damage!", 1000);
                            Enemies[i].TakeDamage(Enemies[i].PoisonDamage);
                        }
                        Enemies[i].TakeTurn(this);
                        Queue.PlayQueue();
                    }
            }
        }

        private void doTurn(Player player)
        {
            // Cursor
            status.ShowCursor(player);

            // Menu -- have the user choose an option.
            cmdMenu.Enabled = true;
            cmdMenu.CursorPosition = 0;
            cmdMenu.CursorVisible = true;
            cmdMenu.WaitForMenu();
            cmdMenu.Enabled = false;

            // We have an action, now do it.
            bool result = false; // stores the result of the action, to check for cancels.
            switch (turnAction)
            {
                case BattleAction.Attack:
                    result = player.Attack(this);
                    break;
                case BattleAction.Arcane:
                    result = player.CastSpell(this, SpellType.Arcane);
                    break;
                case BattleAction.Divine:
                    result = player.CastSpell(this, SpellType.Divine);
                    break;
                case BattleAction.Items:
                    result = player.UseItem(this);
                    break;
            }

            // Check for cancellations
            if (!result)
                doTurn(player);
        }

        private void risingText(Sprite s, string text, GLColor color)
        {
            TextSprite spr = Fonts.Default.DrawText(text);
            spr.Color = color;
            spr.Position = new Point(s.X + (s.UnscaledWidth / 2) - (spr.Width / 2), s.Y + (s.UnscaledHeight / 2) - (spr.Height / 2));
            int initialY = spr.Y;
            Ticker.AddTimer((r, dR) => spr.Y = initialY - (int)(r * 10), 500);
            spr.Dispose();
        }

        private void OnHeal(Combatant c, int amount)
        {
            Sprite s = c.Sprite;

            // Flash the target
            s.Color = new GLColor(0, 0, 0, 1);
            Ticker.AddTimer(null, 100);
            s.Color = new GLColor(1, 1, 1, 1);

            // Show healing
            risingText(s, amount.ToString(), new GLColor(0, 1, 0, 1));
        }

        private void OnEnemyDamage(Combatant c, int amount)
        {
            Sprite s = c.Sprite;

            // Flash the target
            s.Color = new GLColor(0, 0, 0, 1);
            Ticker.AddTimer(null, 100);
            s.Color = new GLColor(1, 1, 1, 1);

            // Show damage
            risingText(s, amount.ToString(), new GLColor(1, 0, 0, 1));
        }

        private void OnEnemyDie(Combatant c)
        {
            Sprite s = c.Sprite;
            Ticker.AddTimer((r, dR) => s.Color.A = (1 - r), 300);
            Enemies.Remove((Enemy)c);
            s.Dispose();

            // Check for victory
            if (Enemies.Count == 0)
            {
                // For now, just exit
                System.Windows.Forms.MessageBox.Show("No more enemies, you win!");
                Program.Running = false;
                Program.BattleThread.Abort();
            }
        }

        public void OnAffliction(Combatant c, BattleAffliction affliction)
        {
            switch (affliction)
            {
                case BattleAffliction.Poisoned:
                    risingText(c.Sprite, "Poisoned!", new GLColor(0, 1, 0, 1));
                    break;
                case BattleAffliction.Asleep:
                    risingText(c.Sprite, "Asleep!", new GLColor(0, 0, 1, 1));
                    break;
            }
        }

        public void Dispose()
        {
            foreach (Combatant c in this.Players)
                c.DeinitialiseSprite();
            foreach (Combatant c in this.Enemies)
                c.DeinitialiseSprite();
        }

        /// <summary>
        /// Checks a target is still alive, to make sure we don't attack blank space.
        /// </summary>
        /// <param name="target">The target we ideally want to attack.</param>
        /// <returns>The target that we will attack, whether it be a different one or not.</returns>
        public Combatant CheckTarget(Combatant target)
        {
            // Right, we have to assume that if an entire party is killed, execution will never reach here
            // as a victory or game over will have been done. We also assume that after an ICombatant is dead
            // they are immediately removed from their respective List<>.

            // Is the target dead?
            if (!target.Alive)
                return (target is Enemy) ? (Combatant)Enemies[0] : (Combatant)Players[0];
            return target;
        }

        public Combatant[] SelectMultipleTargets(bool targetEnemies, bool canSwitchSides)
        {
            // Setup target arrays
            int noTargets = (Enemies.Count > Players.Count) ? Enemies.Count : Players.Count;
            List<Sprite> targets = ((targetEnemies) ? Enemies.ConvertAll<Sprite>(Combatant.ToSprite) : Players.ConvertAll<Sprite>(Combatant.ToSprite));

            // Sort out cursors -- we'll make new ones, uses the same textures.
            AnimatedSprite[] cursors = new AnimatedSprite[noTargets];
            for (int i = 0; i < noTargets; i++)
            {
                cursors[i] = new AnimatedSprite();
                cursors[i].AddAnimation(cursor.CurrentAnimation.Name, cursor.CurrentAnimation.Clone());
                cursors[i].Playing = true;
            }

            // Lovely anonymous function to make code tidier below
            BlankDelegate switchSides = delegate {
                targetEnemies = !targetEnemies;
                targets = ((targetEnemies) ? Enemies.ConvertAll<Sprite>(Combatant.ToSprite) : Players.ConvertAll<Sprite>(Combatant.ToSprite));

                for (int i = 0; i < targets.Count; i++)
                {
                    cursors[i].Playing = true;
                    if (i >= targets.Count)
                        cursors[i].Visible = false;
                    else
                    {
                        cursors[i].Visible = true;
                        if (targetEnemies)
                        {
                            cursors[i].X = targets[i].X - cursor.UnscaledWidth;
                            cursors[i].FlipHorizontal = true;
                        }
                        else
                        {
                            cursors[i].X = targets[i].X + targets[i].UnscaledWidth;
                            cursors[i].FlipHorizontal = false;
                        }
                        cursors[i].Y = targets[i].Y + (targets[i].UnscaledHeight / 2) - (cursors[i].UnscaledHeight / 2);
                    }
                }
            };

            // Setup cursors initially
            targetEnemies = !targetEnemies; // so the delegate doesn't screw us over
            switchSides();

            // Set up events
            bool finished = false;
            KeyEventDelegate d = delegate (Key key)
            {
                switch (key)
                {
                    case Key.Right:
                        if (targetEnemies)
                            switchSides();
                        break;
                    case Key.Left:
                        if (!targetEnemies)
                            switchSides();
                        break;
                    case Key.Select:
                        finished = true;
                        break;
                }
            };
            Keyboard.KeyDown += d;

            // Now wait till we have a selection.
            while (!finished)
            {
                // We'll check for cancelling here, otherwise we could not remove the delegate.
                if (Keyboard.IsPressed(Key.Cancel))
                {
                    Keyboard.KeyDown -= d;
                    for (int i = 0; i < noTargets; i++)
                        cursors[i].Dispose();
                    return null;
                }

                System.Threading.Thread.Sleep(50);
            }

            Keyboard.KeyDown -= d; // If we dont remove the event, all hell breaks lose.
            for (int i = 0; i < noTargets; i++)
                cursors[i].Dispose();
            return (targetEnemies) ? (Combatant[])Enemies.ToArray() : (Combatant[])Players.ToArray();
        }

        public Combatant SelectTarget(bool targetEnemies, bool canSwitchSides, int selected) 
        {
            // If we have a targets array, the same code can select both enemies and players!
            List<Sprite> targets = ((targetEnemies) ? Enemies.ConvertAll<Sprite>(Combatant.ToSprite) : Players.ConvertAll<Sprite>(Combatant.ToSprite));

            // Set up cursor
            if (targetEnemies)
                cursor.FlipHorizontal = true;
            else
                cursor.FlipHorizontal = false;
            cursor.Visible = true;
            cursor.Playing = true;
            if (targetEnemies)
                cursor.X = targets[selected].X - cursor.UnscaledWidth;
            else
                cursor.X = targets[selected].X + targets[selected].UnscaledWidth;
            cursor.Y = targets[selected].Y + (targets[selected].UnscaledHeight / 2) - (cursor.UnscaledHeight / 2);
            
            // Set up events -- it's much better event based, I swear.
            bool finished = false;
            KeyEventDelegate d = delegate(Key key)
            {
                switch (key)
                {
                    case Key.Up:
                        if (selected > 0)
                            selected--;
                        break;
                    case Key.Down:
                        if (selected < (targets.Count - 1))
                            selected++;
                        break;
                    case Key.Right:
                        if (targetEnemies && canSwitchSides)
                        {
                            targetEnemies = false;
                            targets = Players.ConvertAll<Sprite>(Combatant.ToSprite);
                            if (selected > (targets.Count - 1))
                                selected = targets.Count - 1;
                            cursor.FlipHorizontal = (!cursor.FlipHorizontal);
                        }
                        break;
                    case Key.Left:
                        if (!targetEnemies && canSwitchSides)
                        {
                            targetEnemies = true;
                            targets = Enemies.ConvertAll<Sprite>(Combatant.ToSprite);
                            if (selected > (targets.Count - 1))
                                selected = targets.Count - 1;
                            cursor.FlipHorizontal = (!cursor.FlipHorizontal); // Just incase we load a left cursor or idk.
                        }
                        break;
                    case Key.Select:
                        finished = true;
                        break;
                }

                // Reposition Cursor    
                if (targetEnemies)
                    cursor.X = targets[selected].X - cursor.UnscaledWidth;
                else
                    cursor.X = targets[selected].X + targets[selected].UnscaledWidth;
                cursor.Y = targets[selected].Y + (targets[selected].UnscaledHeight / 2) - (cursor.UnscaledHeight / 2);
            };
            Keyboard.KeyDown += d;
            
            // Now wait till we have a selection.
            while (!finished)
            {
                // We'll check for cancelling here, otherwise we could not remove the delegate.
                if (Keyboard.IsPressed(Key.Cancel))
                {
                    Keyboard.KeyDown -= d; 
                    cursor.Playing = false;
                    cursor.Visible = false;
                    return null;
                }

                System.Threading.Thread.Sleep(50);
            }

            Keyboard.KeyDown -= d; // If we dont remove the event, all hell breaks lose.
            cursor.Playing = false;
            cursor.Visible = false; // Both these lines should be one really :/ maybe I already did that.
            return (targetEnemies) ? (Combatant)Enemies[selected] : (Combatant)Players[selected];
        }
    }
}

